/*
 * Penelope.java
 *
 * Defines the Penelope game and user interface.
 */

package game;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.MemoryImageSource;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.SwingConstants;
import javax.swing.Timer;

/**
 * Penelope game. The user either holds or rolls a die. If the die is 1, the
 * user wins nothing. Otherwise, the user gets some number of points equal to
 * the value of the die roll. In free-form play, the user chooses manually when
 * to hold and when to roll. In structured play, the user defines an heuristic
 * to follow, which is executed automatically. In the question round, the user
 * is asked if they think they had the best strategy, and, if they respond "no",
 * they are asked to guess the best strategy.
 *
 * @author Justin Huang
 */
public class Penelope extends Game {
    /** The amount of time in seconds the user has to play the game. */
    final int GAME_TIME = 180;

    /** The number of free-form play rounds. */
    int freeRounds;
    
    /** The number of structured play rounds. */
    int structRounds;
    
    /** The dollar amount the user gets per point earned. */
    double payPerPoint;
    
    /** The total amount of points the user currently has. */
    int points;

    /** The amount of points the user has earned this round. */
    int roundPoints;

    /** The number of points the structured play strategy will stop rolling at.
     */
    int maxStructPoints;

    /** The last result of a roll of the die. -1 if the die hasn't been rolled
     * before. */
    int lastRoll;

    /** What the user thinks is the optimal strategy by the end of the game. */
    int userOptimalStrat;

    /** The current state of the game (free-form = 0, set strategy = 1,
     * structured play = 2, question = 3, end of game = 4). */
    int gameState;

	/** The dice rolls for the current structured play round. */
	ArrayList<Integer> roundDiceList;

    // Game state constants.
    final int GS_FREE_FORM = 0;
    final int GS_FREE_FORM_ROUNDOVER = 1;
    final int GS_STRUCT_SET = 2;
    final int GS_STRUCT_PLAY = 3;
    final int GS_QUESTION = 4;
    final int GS_QUESTION_NO = 5;
    final int GS_END_OF_GAME = 6;

	// Game log constants.
	final int EG_ROUNDTYPE_FF = 1;
	final int EG_ROUNDTYPE_SR = 2;
	final int EG_ROUNDTYPE_QA = 3;
	final int EG_ROLL = 0;
	final int EG_HOLD = 1;

    /** The current round we're on, which resets after every new gameplay
     * type. */
    int currentRound;

    /** Timer for the game countdown. */
    Timer timer = new Timer (1000, new TimeListener());

	/** Seconds remaining for round over message. */
	int roundOverSecs;

	/** Amount of time, in seconds, we want to display the round over message.
	 */
	final int ROUND_OVER_WAIT = 4;

	/** Timer for round over message. */
	Timer roundOverTimer = new Timer (1000, new TimeListener());

    /** Timer for dice rolls. */
    Timer diceTimer = new Timer (750, new TimeListener());

	/** Amount of time, in seconds, the subject spent making their decision. */
	int playerThinkTime = 0;

    /**
     * Default constructor.
     */
    public Penelope() {
        super();
        initComponents();
        this.setVisible (true);
        hideMouse();
    }

    /**
     * Constructor for Penelope.
     *
     * @param params		The parameters passed to the game.
	 * @param currentRep	How many times we've played this game before + 1.
     */
    public Penelope (String params, int currentRep) {
        super();

		this.params = params;
		String paramPart[] = params.split (",");
		repTot = Integer.parseInt (paramPart[1]);
        freeRounds = Integer.parseInt (paramPart[2]);
        structRounds = Integer.parseInt (paramPart[3]);
		payPerPoint = Double.parseDouble (paramPart[4]);
		repNum = currentRep;

		initComponents();
        this.setVisible (true);
        hideMouse();

        secs = GAME_TIME;
		updateTimerLabel();
        points = 0;
        roundPoints = 0;
        currentRound = 1;
        lastRoll = -1;
        userOptimalStrat = 0;

        timer.start();

        setFreeFormLayout();
        gameState = GS_FREE_FORM;

        runner.addMouseListener (new Penelope.CheckMouse());
        this.addMouseListener (new Penelope.CheckMouse());
		this.getGlassPane().addMouseListener(new Penelope.CheckMouse());
    }

    /**
     * Mouse click handler, delegates to handleLeftClick and handleRightClick
     */
    class CheckMouse implements MouseListener{
        public void mousePressed (MouseEvent e) { hideMouse(); }
        public void mouseReleased (MouseEvent e) {}
        public void mouseEntered (MouseEvent e) {}
        public void mouseExited (MouseEvent e) {}
        
        /**
         * Handle mouse clicks. If the user left clicks, we end the game. If
         * the user right clicks, we roll the dice again.
         * 
         * @param e The MouseEvent.
         */
        public void mouseClicked (MouseEvent e){
            int modifiers = e.getModifiers();

            // Left click.
            if ((modifiers & InputEvent.BUTTON1_MASK) ==
                    InputEvent.BUTTON1_MASK) {
                handleLeftClick();
            }

            // Right click.
            else if ((modifiers & InputEvent.BUTTON3_MASK) ==
                    InputEvent.BUTTON3_MASK) {
                handleRightClick();
            }

            // Invalid input.
            else {
                // Do nothing.
            }
        }
    }

	/**
	 * Called on left click. Decides what to do based on what game state we're
	 * in.
	 */
	private void handleLeftClick() {
		switch (gameState) {
		case GS_FREE_FORM: {
			int result = rollDie();
			if (result == 1) {
				roundPoints = 0;

				writeGameLog (playerId + "," + playerThinkTime + ","
					+ EG_ROUNDTYPE_FF + "," + currentRound + "," + freeRounds
					+ "," + roundPoints + ",," + EG_ROLL + ",\"1\"");
				playerThinkTime = 0;

				currentRound++;
				gameState = GS_FREE_FORM_ROUNDOVER;
				roundOverSecs = ROUND_OVER_WAIT;
				roundOverTimer.start();
				setRoundOverLayout();
			}
			else {
				roundPoints += result;
				writeGameLog (playerId + "," + playerThinkTime + ","
					+ EG_ROUNDTYPE_FF + "," + currentRound + "," + freeRounds
					+ "," + roundPoints + ",," + EG_ROLL + ",\"" + result
					+ "\"");
				playerThinkTime = 0;
			}

			updateMoneyLabel();
			updateRoundLabel();

			break;
		}
		case GS_FREE_FORM_ROUNDOVER: {
			break;
		}
		case GS_STRUCT_SET: {
			maxStructPoints++;
			updateMoneyLabel();
			break;
		}
		case GS_STRUCT_PLAY: {
			if (currentRound > structRounds) {
				 gameState = GS_QUESTION;
				 setQuestionLayout();
			}
			else {
				roundPoints = 0;
				structuredPlayRound();
				currentRound++;
			}
			break;
		}
		case GS_QUESTION: {
			timer.stop();
			gameState = GS_END_OF_GAME;
			userOptimalStrat = maxStructPoints;
			totalMoneyWon += points * payPerPoint;
			writeLog (playerId + ",,\"" + params + "\"," + repNum + "," + repTot
				+ "," + String.format ("$%.2f", points * payPerPoint) + ",");
			writeGameLog (playerId + "," + playerThinkTime + ","
				+ EG_ROUNDTYPE_QA + ",1,1,," + maxStructPoints + "," + EG_HOLD
				+ ",");
			playerThinkTime = 0;

			try {
				setVisible(false);
				free=true;
			}
			catch(Exception ex) {
				ex.printStackTrace();
				logError(ex);
			}
			break;
		}
		case GS_QUESTION_NO: {
			userOptimalStrat++;
			updateMoneyLabel();
			break;
		}
		default:
		}
	}

	/**
	 * Called on right click. Decides what to do based on what game state we're
	 * in.
	 */
	private void handleRightClick() {
		switch (gameState) {
		case GS_FREE_FORM: {
			points += roundPoints;

			writeGameLog (playerId + "," + playerThinkTime + ","
				+ EG_ROUNDTYPE_FF + "," + currentRound + "," + freeRounds + ","
				+ roundPoints + ",," + EG_HOLD + ",");
			playerThinkTime = 0;

			roundPoints = 0;
			currentRound++;
			gameState = GS_FREE_FORM_ROUNDOVER;
			roundOverSecs = ROUND_OVER_WAIT;
			roundOverTimer.start();
			setRoundOverLayout();

			updateMoneyLabel();
			updateRoundLabel();

			break;
		}
		case GS_FREE_FORM_ROUNDOVER: {
			break;
		}
		case GS_STRUCT_SET: {
			gameState = GS_STRUCT_PLAY;
			lastRoll = -1;
			timer.stop();
			setStructPlayLayout();
			break;
		}
		case GS_STRUCT_PLAY: {
			if (currentRound > structRounds) {
				gameState = GS_QUESTION;
				setQuestionLayout();
			}
			else {
				roundPoints = 0;
				structuredPlayRound();
				currentRound++;
			}
			break;
		}
		case GS_QUESTION: {
			gameState = GS_QUESTION_NO;
			setQuestionNoLayout();
			break;
		}
		case GS_QUESTION_NO: {
			timer.stop();
			gameState = GS_END_OF_GAME;
			totalMoneyWon += points * payPerPoint;
			writeLog (playerId + ",,\"" + params + "\"," + repNum + "," + repTot
				+ "," + String.format ("$%.2f", points * payPerPoint) + ",");
			writeGameLog (playerId + "," + playerThinkTime + ","
				+ EG_ROUNDTYPE_QA + ",1,1,," + userOptimalStrat + "," + EG_ROLL
				+ ",");
			playerThinkTime = 0;

			try {
				setVisible(false);
				free=true;
			}
			catch(Exception ex) {
				ex.printStackTrace();
				logError(ex);
			}
			break;
		}
		default:
		} // switch (gameState)
	}
    // GAME --------------------------------------------------------------------
    /**
     * Simulates the user's strategy.
     */
    private void structuredPlayRound() {
        updateRoundLabel();
        historyLabel.setVisible (true);
        diceOutcomesField.setVisible (true);
        statusLabel.setVisible (true);
        diceLabel.setVisible (true);

		roundDiceList = new ArrayList<Integer>();
        diceTimer.start();
        actionLabel.setText ("Rolling...");
    }

    /**
     * Rolls a die, sets the image in the user interface, and returns the value
     * that was rolled.
     */
    private int rollDie() {
        Random die = new Random();
        int roll = die.nextInt(6) + 1;

        lastRoll = roll;
        updateDiceLabel();

        return roll;
    }

    // LAYOUT ------------------------------------------------------------------
    /**
     * Sets the layout of the game for the free-form game.
     */
    private void setFreeFormLayout() {
        updateRoundLabel();
        updateMoneyLabel();
        lblPass.setVisible(true);
        lblStop.setVisible(true);
        actionLabel.setText ("What would you like to do?");
        historyLabel.setText("");
        diceLabel.setHorizontalAlignment(SwingConstants.LEFT);       

        if (lastRoll == -1) statusLabel.setText("");
        else {
            statusLabel.setText ("The last roll was " + lastRoll);
            updateDiceLabel();
        }
    }

    /**
     * Tells the user they landed on 1, and click to continue.
     */
    private void setRoundOverLayout() {
        lblPass.setVisible (false);
        lblStop.setVisible (false);
        actionLabel.setText ("Round over. Continuing in a few moments...");
    }

    /**
     * Sets the layout when the user defines the strategy for structured play.
     */
    private void setStructSetLayout() {
        updateRoundLabel();
        statusLabel.setText("");
        maxStructPoints = 0;
        updateMoneyLabel();
        moneyLabel.setText("Hold until I have 0 points.");
        diceLabel.setVisible (false);
        historyLabel.setText ("");
        actionLabel.setText("Set a strategy to automatically play this game.");

        lblPass.setIcon (new ImageIcon (this.getClass().getResource
                    ("/images/redAddPoints.png")));
        lblStop.setIcon (new ImageIcon (this.getClass().getResource
                    ("/images/greenDone.png")));
        lblPass.setVisible (true);
        lblStop.setVisible (true);
    }

    /**
     * Sets the layout when we want to just simulate the user given strategy.
     */
    private void setStructPlayLayout() {
        updateRoundLabel();

        lblTime.setVisible (false);

        statusLabel.setHorizontalAlignment (SwingConstants.CENTER);
        diceLabel.setVisible (true);
        diceLabel.setIcon (null);
        diceLabel.setHorizontalAlignment (SwingConstants.CENTER);

        historyLabel.setText ("Previous Rolls");
        historyLabel.setVisible (false);

        actionLabel.setText ("Click to simulate your strategy for a round.");
        lblPass.setVisible (false);
        lblStop.setVisible (false);
    }

    /**
     * Sets the layout of the game for the question game.
     */
    private void setQuestionLayout() {
        secs = GAME_TIME;
		updateTimerLabel();
        timer.start();
        lblTime.setVisible (true);

        // We will use the money label to ask the questions from now on.
        moneyLabel.setForeground(new Color (0, 0, 0));
        moneyLabel.setText ("There is a strategy that is best. Do you think you"
                + " chose it?");

        historyLabel.setVisible (false);
        diceOutcomesField.setText("");
        statusLabel.setVisible (false);
        diceLabel.setVisible (false);

        actionLabel.setVisible (false);
        lblPass.setIcon (new ImageIcon (this.getClass().getResource
                    ("/images/redYes.png")));
        lblStop.setIcon (new ImageIcon (this.getClass().getResource
                    ("/images/greenNo.png")));
        lblPass.setVisible (true);
        lblStop.setVisible (true);
    }

    /**
     * If the user answers no to the first question, then we ask what they think
     * the optimal strategy is, using a layout similar to when they initially
     * chose their strategy.
     */
    private void setQuestionNoLayout() {
        setStructSetLayout();
        moneyLabel.setForeground (new Color (0, 51, 255));
        actionLabel.setText ("What do you think is the best strategy?");
        actionLabel.setVisible (true);
    }

    // USER INTERFACE ----------------------------------------------------------
    /**
     * Displays what part of the game, and what round we're on.
     */
    private void updateRoundLabel() {
        switch (gameState) {
            case GS_FREE_FORM: roundLabel.setText
                    ("Free-form Play - Round " + currentRound + " of "
                    + freeRounds); break;
            case GS_FREE_FORM_ROUNDOVER: roundLabel.setText
                    ("Free-form Play"); break;
            case GS_STRUCT_SET: roundLabel.setText
                    ("Structured Play - Define Strategy"); break;
            case GS_STRUCT_PLAY: roundLabel.setText
                    ("Structured Play - Round " + currentRound + " of "
                    + structRounds); break;
            case GS_QUESTION:
            case GS_QUESTION_NO: roundLabel.setText
                    ("Question Session"); break;
            default: roundLabel.setText ("Penelope");
        }
    }

    /**
     * Displays how much the player has won, or if we are setting the structured
     * play strategy, displays the strategy.
     */
    private void updateMoneyLabel() {
        if (gameState == GS_STRUCT_SET) {
            moneyLabel.setText("Hold until I have " + maxStructPoints
                    + " point" + ((maxStructPoints == 1) ? "." : "s."));
        }
        else if (gameState == GS_QUESTION_NO) {
            moneyLabel.setText("Hold until I have " + userOptimalStrat
                    + " point" + ((userOptimalStrat == 1) ? "." : "s."));
        }
        else if (gameState == GS_FREE_FORM)
        {
            int totalPoints = points + roundPoints;
            String money = "You have " + totalPoints;
            if (totalPoints == 1) money += " point, ";
            else money += " points, ";
            money += "earning you $" + String.format("%.2f",
                    totalPoints * payPerPoint);
            moneyLabel.setText(money);
        }
        else {
            String money = "You have " + points;
            if (points == 1) money += " point, ";
            else money += " points, ";
            money += "earning you $" + String.format("%.2f",
                    points * payPerPoint);
            moneyLabel.setText(money);
        }
    }

    /**
     * Displays a picture of the last rolled die.
     */
    private void updateDiceLabel() {
        if (lastRoll != -1) {
            statusLabel.setText("The last roll was: " + lastRoll);
            diceLabel.setIcon (new ImageIcon (this.getClass().getResource
                        ("/images/dice" + lastRoll + ".png")));
        }
    }

    /**
     * Based on the time remaining, updates the time remaining label.
     */
    private void updateTimerLabel() {
        int min = secs / 60;
        int sec = secs % 60;
        lblTime.setText (min + ":" + ((sec < 10) ? "0" : "") + sec);
    }

    /**
     * Hides the mouse so the user can't see it.
     */
    private void hideMouse() {
        int[] pixels = new int[16 * 16];
        Image image = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(16, 16, pixels, 0, 16));
        Cursor transparentCursor = Toolkit.getDefaultToolkit().createCustomCursor(image, new Point(0, 0), "invisibleCursor");
        this.getGlassPane().setCursor(transparentCursor);
		this.getGlassPane().setVisible(true);
        runner.setCursor(transparentCursor);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        lblTime = new javax.swing.JLabel();
        roundLabel = new javax.swing.JLabel();
        actionLabel = new javax.swing.JLabel();
        moneyLabel = new javax.swing.JLabel();
        lblStop = new javax.swing.JLabel();
        lblPass = new javax.swing.JLabel();
        statusLabel = new javax.swing.JTextField();
        diceLabel = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        diceOutcomesField = new javax.swing.JTextArea();
        historyLabel = new javax.swing.JLabel();

        setBackground(new java.awt.Color(255, 255, 255));
        setBorder(null);
        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        setMaximumSize(new java.awt.Dimension(800, 600));
        setMinimumSize(new java.awt.Dimension(800, 600));
        setPreferredSize(new java.awt.Dimension(800, 600));

        lblTime.setFont(new java.awt.Font("Calibri", 1, 36));
        lblTime.setForeground(new java.awt.Color(153, 102, 0));
        lblTime.setIcon(new javax.swing.ImageIcon(getClass().getResource("/images/hrGlass.png"))); // NOI18N
        lblTime.setText("3:00");

        roundLabel.setFont(new java.awt.Font("Calibri", 1, 28));
        roundLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        roundLabel.setText("Free-form Play - Round 1");

        actionLabel.setFont(new java.awt.Font("Calibri", 1, 32));
        actionLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        actionLabel.setText("What would you like to do?");

        moneyLabel.setFont(new java.awt.Font("Calibri", 1, 32));
        moneyLabel.setForeground(new java.awt.Color(0, 51, 255));
        moneyLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        moneyLabel.setText("You have points, earning you $");

        lblStop.setFont(new java.awt.Font("Trebuchet MS", 1, 24)); // NOI18N
        lblStop.setIcon(new javax.swing.ImageIcon(getClass().getResource("/images/redHold.png"))); // NOI18N

        lblPass.setFont(new java.awt.Font("Trebuchet MS", 1, 24)); // NOI18N
        lblPass.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        lblPass.setIcon(new javax.swing.ImageIcon(getClass().getResource("/images/greenRoll.png"))); // NOI18N

        statusLabel.setBackground(new java.awt.Color(255, 255, 255));
        statusLabel.setEditable(false);
        statusLabel.setFont(new java.awt.Font("Calibri", 1, 28)); // NOI18N
        statusLabel.setHorizontalAlignment(javax.swing.JTextField.LEFT);
        statusLabel.setText("The last roll was");
        statusLabel.setBorder(null);
        statusLabel.setFocusable(false);

        diceLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
        diceLabel.setFocusable(false);

        jScrollPane1.setBorder(null);
        jScrollPane1.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        diceOutcomesField.setColumns(39);
        diceOutcomesField.setEditable(false);
        diceOutcomesField.setFont(new java.awt.Font("Consolas", 0, 14));
        diceOutcomesField.setLineWrap(true);
        diceOutcomesField.setRows(5);
        diceOutcomesField.setBorder(null);
        diceOutcomesField.setFocusable(false);
        jScrollPane1.setViewportView(diceOutcomesField);

        historyLabel.setFont(new java.awt.Font("Tahoma", 0, 14));
        historyLabel.setText("Previous Rolls");

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .add(lblTime, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 819, Short.MAX_VALUE)
                .add(36, 36, 36))
            .add(layout.createSequentialGroup()
                .addContainerGap()
                .add(roundLabel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 835, Short.MAX_VALUE)
                .addContainerGap())
            .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .add(moneyLabel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 835, Short.MAX_VALUE)
                .addContainerGap())
            .add(layout.createSequentialGroup()
                .add(20, 20, 20)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                    .add(layout.createSequentialGroup()
                        .add(historyLabel)
                        .add(143, 143, 143))
                    .add(layout.createSequentialGroup()
                        .add(jScrollPane1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 332, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)))
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                    .add(statusLabel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 487, Short.MAX_VALUE)
                    .add(org.jdesktop.layout.GroupLayout.LEADING, diceLabel, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 487, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addContainerGap())
            .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(actionLabel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 835, Short.MAX_VALUE)
                    .add(layout.createSequentialGroup()
                        .add(94, 94, 94)
                        .add(lblPass)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, 181, Short.MAX_VALUE)
                        .add(lblStop)
                        .add(94, 94, 94)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .add(lblTime)
                .add(18, 18, 18)
                .add(roundLabel)
                .add(18, 18, 18)
                .add(moneyLabel, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 70, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                .add(22, 22, 22)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                    .add(historyLabel)
                    .add(statusLabel, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 146, Short.MAX_VALUE)
                    .add(diceLabel, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 146, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(actionLabel)
                .add(40, 40, 40)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                    .add(lblPass)
                    .add(lblStop))
                .add(62, 62, 62))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    /**
    * @param args the command line arguments
    */
    public static void main(String args[]) {
        try {
            JFrame main = new JFrame();
            JDesktopPane dPane = new JDesktopPane();
            Penelope frame = new Penelope();
            main.setSize(820, 620);
            dPane.add(frame);
            main.add(dPane);
            main.setVisible(true);
            dPane.setVisible(true);
            frame.setSelected(true);
            frame.setVisible(true);
        }
        catch(Exception ex) {
            ex.printStackTrace();
        }
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JLabel actionLabel;
    private javax.swing.JLabel diceLabel;
    private javax.swing.JTextArea diceOutcomesField;
    private javax.swing.JLabel historyLabel;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JLabel lblPass;
    private javax.swing.JLabel lblStop;
    private javax.swing.JLabel lblTime;
    private javax.swing.JLabel moneyLabel;
    private javax.swing.JLabel roundLabel;
    private javax.swing.JTextField statusLabel;
    // End of variables declaration//GEN-END:variables

    /**
     * Handles timer actions.
     */
    class TimeListener implements ActionListener {
    	/**
         * Decrement the time remaining, if there is any. If the user has run
         * out of time, then record the amount of money won and exit.
         * 
         * @param e The ActionEvent.
         */
         public void actionPerformed(ActionEvent e) {
            if (gameState == GS_FREE_FORM || gameState == GS_STRUCT_SET
                || gameState == GS_QUESTION || gameState == GS_QUESTION_NO) {
                if (secs > 0) {
                    secs--;
					playerThinkTime++;
                    updateTimerLabel();
                }
                else {
                    timer.stop();

                    statusLabel.setText ("Sorry, your time is up.");
                    gameState = GS_END_OF_GAME;

                    writeLog (playerId + ",,\"" + params + "\"," + repNum + ","
						+ repTot + ",TimeOut,TimeOut");
                    totalMoneyWon += points * payPerPoint;

                    try {
                        setVisible(false);
                        free=true;
                    }
                    catch(Exception ex) {
                        ex.printStackTrace();
                        logError(ex);
                    }
                }
            }
			if (gameState == GS_FREE_FORM_ROUNDOVER) {
				roundOverSecs--;
				if (roundOverSecs <= 0) {
					if (currentRound > freeRounds) {
						currentRound = 1;
						gameState = GS_STRUCT_SET;
						setStructSetLayout();
					}
					else {
						gameState = GS_FREE_FORM;
						points += roundPoints;
						roundPoints = 0;
						setFreeFormLayout();
					}
					roundOverTimer.stop();
				}
			}
            if (gameState == GS_STRUCT_PLAY) {
                lastRoll = rollDie();

				// Used for game log.
				roundDiceList.add (lastRoll);
				String diceStr = "";
				for (int i=0; i<roundDiceList.size(); i++) {
					diceStr += roundDiceList.get(i);
					if (i != roundDiceList.size()-1)
						diceStr += ",";
				}

                if (lastRoll == 1) {
                    roundPoints = 0;
                    diceOutcomesField.setText(diceOutcomesField.getText()
                        + "1");
                }
                else {
                    roundPoints += lastRoll;
                    diceOutcomesField.setText(diceOutcomesField.getText()
                        + lastRoll
                        + ((roundPoints < maxStructPoints) ? ", " : ""));
                }

                if (lastRoll == 1 || roundPoints >= maxStructPoints) {
                    writeGameLog (playerId + ",,"
						+ EG_ROUNDTYPE_SR + "," + (currentRound-1) + ","
						+ structRounds + "," + roundPoints + ","
						+ maxStructPoints + ",,\"" + diceStr + "\"");
					playerThinkTime = 0;

					diceTimer.stop();
                    points += roundPoints;
                    if (currentRound <= structRounds)
                        actionLabel.setText ("Click to simulate another round.");
                    else
                        actionLabel.setText ("Click to continue onto the "
                                     + "questions.");
                    diceOutcomesField.setText(diceOutcomesField.getText()
                            + "\n\n");
                    updateMoneyLabel();
                }
            }
    	}
    }
}
